Skip to content

Generate TypeScript and Python route snippets from openapi.yaml - #461

Merged
Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
Naajih09:docs/277-route-snippets
Oct 25, 2026
Merged

Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
Naajih09:docs/277-route-snippets

Conversation

@Naajih09

Copy link
Copy Markdown
Contributor

Body:

Closes #277

What this does

Adds a generator that writes TypeScript and Python snippets for each GET route group in openapi.yaml, so the snippets are derived from the spec and cannot drift from it.

  • scripts/gen-snippets.ts: reads openapi.yaml, groups GET routes by path prefix (price, analytics, service), and renders one .ts and one .py file per group under snippets/.
  • tests/snippets.test.ts: checks that the generator is deterministic, that the committed snippet files match a fresh render, and that no snippet contains a placeholder host or address.
  • package.json: adds snippets:gen.

The generator fails the build if a GET route is neither in a group nor listed in SKIPPED, so a new route cannot be dropped silently.

Running the snippets

Each snippet reads its configuration from the environment and fails with a clear message if something is missing:

  • LENS_BASE_URL (required): the live service URL.
  • STELLAR_SECRET (TypeScript, gated routes): a testnet secret key used to sign x402 payments.
  • LENS_X_PAYMENT (Python, gated routes): a pre-signed base64 X-PAYMENT header.
  • LENS_API_KEY (/usage/me): a client API key.

No key, secret, or address is written into any snippet.

Known limits

  • Live base URL. I don't have the public URL. openapi.yaml lists localhost:4000, the README lists localhost:3002, and neither is a live service. I need the maintainers to confirm it before this can be verified end to end.
  • Python x402. The TypeScript gated snippets use @x402/fetch. I haven't confirmed a Python signer, so the Python gated snippets expect a pre-signed LENS_X_PAYMENT header. This is a stopgap.
  • Not run end to end. I haven't executed the generator or the snippets against a live service. The first npm run snippets:gen and npm test may surface errors.
  • Skipped routes. POST and DELETE operations are not generated, because they need request bodies, admin keys, or signed payment payloads. /graphql is skipped because docs/graphql.md covers it.
  • Spec disagreement. The README says /graphql is not payment-gated, and the spec marks its POST as gated. The generator follows the spec. Please confirm which is correct.
  • Network default. The spec defaults network to testnet. Snippets send that default explicitly.

@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@Naajih09 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Miracle656 added a commit that referenced this pull request Oct 22, 2026
  tsc --noEmit   clean
  vitest         99 files, 1140 passing (up from 97 / 1135)
  dropped-tests  none

  #280  close the vulnerabilities that are actually reachable
  #426  keep watched pairs as metric labels, bounded
  #529  GET /price/:asset/fiat for local currency
  #530  standard 429 response contract with retryAfter

#530 pairs well with what landed earlier: #307 standardised empty-vs-missing
status codes, and this does the same for rate limiting. An API that answers
429 without telling a client when to come back forces every consumer to invent
its own backoff, and they will not agree.

#461 (generate TypeScript and Python route snippets from the OpenAPI spec) was
dropped. It fails three of its own tests even after running `npm run
snippets:gen`:

  × render deterministically
  × committed files match the generator
  × contain no placeholder hosts or addresses

The regeneration produced no files at all, so the generator is not doing what
the test expects of it. The third case is the one I care about most -- a
snippet carrying a placeholder address is exactly how an invented contract id
reaches a contributor, which has already cost us a sweep. Worth getting right
rather than merging around.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Held — three of this PR's own tests fail, and they still fail after running the generator:

× render deterministically
× committed files match the generator
× contain no placeholder hosts or addresses

I ran npm run snippets:gen on the merged tree and it produced no files at all — git status was clean afterwards. So either the generator is not writing where the test looks, or it is exiting before it writes. Worth checking that first; the other two failures may well fall out of it.

The third case is the one I would fix even if the others were passing. A snippet that ships a placeholder host or address is precisely how an invented contract id reaches a contributor — a previous issue batch of ours shortened one contract id and five people invented their own to fill the gap, which cost a full review sweep to untangle. A generated snippet is worse again, because it looks authoritative and people paste it.

So: every address in a generated snippet should be either a real, full, 56-character value from the spec, or so obviously fake that nobody could mistake it for one. Nothing in between.

The idea is good and I want it — a client snippet that cannot drift from the spec is worth having, and it pairs with #403's generated TypeScript client. Get the generator writing and the three tests green and I will take it.

@Naajih09

Naajih09 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

@Miracle656
Thanks for the detailed review. The generator was failing on unlisted routes and missing spec examples, so I fixed both:

  • Every GET route is now in a snippet group or in SKIPPED with a reason. /venues/registry and /snapshot are in service; /live and /ready are skipped as orchestrator probes.
  • Every path parameter in openapi.yaml has an example, so snippets no longer rely on guessed values.
  • The truncated USDC:GBBD47... example on POST /pairs is now the full issuer.
  • openapi.json is regenerated to match the spec.
  • The only addresses in the generated snippets are full 56-character values from the spec. No placeholder hosts.

npm test passes (911 tests, one pre-existing skip). The generator now writes the snippet files.
image

@Naajih09
Naajih09 requested a review from Miracle656 October 25, 2026 06:27
@Miracle656

Copy link
Copy Markdown
Collaborator

Status update — this is close, and the blocker is mechanical.

Against current main it fails three tests, all from one cause:

tests/snippets.test.ts
  × render deterministically
  × committed files match the generator
  × contain no placeholder hosts or addresses

Error: GET /tvl is not in a snippet group or SKIPPED in scripts/gen-snippets.ts

That guard is the best thing in the PR, so I want to be clear I am not asking you to weaken it. Refusing to generate when a route belongs to no group is what stops the snippets quietly covering 60% of the API while looking complete — the exact failure mode the hand-written curl examples had. Keep it exactly as it is.

What it needs is the route list catching up. main has gained a lot since you branched: 15 paths are currently in neither a group's prefixes nor SKIPPED:

/live                          /ready                 /graphql
/tvl                           /rates                 /verify
/impact/{assetA}/{assetB}      /rates/{asset}         /settle
/dispersion                    /movers                /webhooks
/dispersion/{assetA}/{assetB}  /correlation           /webhooks/{id}

Most are a one-line addition to an existing group. A few are judgement calls worth making deliberately rather than sweeping into SKIPPED:

  • /live, /ready — health probes. A snippet is arguably noise; SKIPPED with a reason seems right.
  • /verify, /settle — x402 facilitator endpoints, called by the payment library rather than by hand. Probably SKIPPED, but a snippet showing the shape might genuinely help.
  • /graphql — not a REST call; a curl/TS/Python snippet would need a query body. Either its own group with a worked example, or SKIPPED pointing at docs/graphql.md.
  • /webhooks, /webhooks/{id} — POST and DELETE rather than GET. Worth checking the generator handles non-GET methods before grouping them.

The rest (/tvl, /rates, /movers, /correlation, /dispersion, /impact) are ordinary read endpoints that should slot into existing groups.

Once the list is complete, regenerate the committed snippets so committed files match the generator passes:

npx tsx scripts/gen-snippets.ts

Then a full npm test. Everything else in the PR already passes — 1,603 of 1,606 — so this is the last step.

One suggestion for the medium term, not now: this is the fifth generated artifact in the repo that a stale route list can break (openapi.json, the TypeScript client, schema.graphql, the README tables, and now snippets). Each one fails loudly, which is right, but they all fail for the same reason — a route was added and one list was not updated. Worth an issue about collapsing them behind a single npm run generate once this lands.

@Miracle656

Copy link
Copy Markdown
Collaborator

Merged into main (47dff53). The design decision I like most here is that the generator refuses to guess:

Required parameter "asset" has no example in openapi.yaml.
Add one to the spec rather than guessing a value here.

A snippet generator that invents a plausible-looking asset code produces copy-pasteable commands that fail for the reader, which is worse than no snippet. Throwing instead pushes the fix into the spec, where it benefits the docs and the client too. Byte-comparing the committed snippets against a fresh render is the right guard for the same reason.

Three things had to be reconciled with main, all of them things that landed after you cut the branch rather than mistakes on your side.

Grouping had to move to the bare route. Since 97c50bf every documented path carries /v1, so path.startsWith('/venues') matched nothing and the generator threw on the first route it reached. It now normalises with toBarePath for group and skip-list matching, while the rendered snippet still shows the versioned URL — that is the one callers should use, and it is the one the spec documents.

Eight GET routes had no group. This is the list I flagged last round, now resolved rather than enumerated: /tvl, /rates, /rates/{asset}, /dispersion, /dispersion/{assetA}/{assetB}, /movers, /correlation and /impact. I put /impact with price — what a trade of a given size costs is the same question /book and the depth routes answer — and the rest with analytics. Move them if you disagree; the grouping is editorial and you own that file now.

Two required parameters had no example, which is exactly the failure mode the generator is designed for. Covering those routes surfaced asset on /price/{asset}/fiat and q on /discovery/search. I added XLM and USDC to openapi.yaml rather than special-casing them in the script.

That last point connects to something worth knowing: README.md's generated endpoint table still has zero rows carrying a query string, so /v1/impact/{assetA}/{assetB} is documented there without any hint of ?thresholds=. Your spec examples are now the raw material to fix that. If you want a follow-up with real appetite behind it, teaching scripts/generate-readme.ts to append examples for required query parameters would make that table genuinely copy-pasteable — same idea as this PR, one layer up.

tsc clean, readme:check clean, 1,737 tests pass.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656

Copy link
Copy Markdown
Collaborator

Closing as merged — your work is on main in 47dff53, with the detail in the review comment above.

It does not show as auto-merged because you pushed 245ac4c (a merge of upstream/main) while I was verifying, so the head SHA moved after I fetched. I checked that commit: it carries no new work of yours beyond the merge, and merging it now would revert the three fixes in 47dff53 — the toBarePath grouping, the eight newly grouped routes, and the two spec examples. So main has the newer version, not this branch.

Nothing of yours was dropped: scripts/gen-snippets.ts, both snippet languages and tests/snippets.test.ts all landed.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656 Miracle656 closed this Oct 25, 2026
@Naajih09

Copy link
Copy Markdown
Contributor Author
image I have been here trying to fix things but you closed it

@Miracle656 Miracle656 reopened this Oct 25, 2026
@Miracle656
Miracle656 merged commit 50ebcb1 into Divinity-Labs:main Oct 25, 2026
@Miracle656

Copy link
Copy Markdown
Collaborator

@Naajih09 — my apologies for the timing. You were mid-fix and I closed this out from under you. That was avoidable and I should have checked before closing rather than after.

To be clear about where things stand: your work is merged, and this PR is now correctly marked as merged rather than closed. I have reopened it and recorded the merge so the record matches what actually happened.

Here is exactly what landed and where:

  • 47dff53 — your scripts/gen-snippets.ts, both snippet languages (snippets/typescript/*, snippets/python/*) and tests/snippets.test.ts, with three fixes I made during review: toBarePath grouping for the /v1 paths, eight newly grouped routes, and two missing spec examples.
  • 99557d8 — CRLF normalisation in your snippet test, so it stops false-failing on Windows checkouts.
  • 50ebcb1 — a -s ours merge of your branch. It changes no files; git diff origin/main HEAD is empty. It exists purely so this PR registers as merged under your name.

You can verify the first one yourself: git log --oneline 47dff53 and git show 47dff53 --stat.

What happened on my side, since you deserve the actual reason rather than an apology alone: I fetched your branch, reviewed it, fixed three things that broke when /v1 versioning landed earlier today, merged the result to main, and then found that you had pushed 245ac4c while I was verifying. I checked that commit — it was a merge of upstream/main into your branch with no new work of yours — so I closed the PR as merged. The reasoning was right, but closing a PR whose author is visibly still working on it is the wrong call regardless, and I should have said something here first.

One thing worth knowing: your branch at 245ac4c is now a subset of main for these files. If you pull and keep working from it, you will be working against an older version and may re-introduce the /v1 grouping bug. Start from current main instead.

If you want the natural follow-up, there is a real one and it is yours if you want it. The generated README endpoint table has zero rows carrying a query string:

$ grep -cE '^\| (GET|POST) \| `[^`]*\?' README.md
0

so /v1/impact/{assetA}/{assetB} is documented with no hint of ?thresholds=, and that table is what people copy into a terminal. The spec now has example: values on parameters — thanks partly to this PR — so teaching scripts/generate-readme.ts to append examples for required query parameters would make the table genuinely copy-pasteable. Same idea as your snippets, one layer up. Open it against current main and I will review it the same day.

Thanks for the work, and sorry again for the way this went.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route code snippets exist in no language but curl

2 participants